#include "vec.h"
#include "particle.h"

// The grid - a digital frontier
enum GridMode { HIDDEN, ALL, OCCUPIED_ONLY, MINECRAFT };

class Grid {
public:
	GLfloat m_cellSpacing;
	vec3f m_gridResolution;
	vec3f m_volMin;
	vec3f m_volMax;
	std::vector< std::vector<Particle*> > m_cells;
	// Grid is made by several stacks (z-axis) consisting of rows(y-axis) and columns (x-axis)
	GLfloat m_cellsPerStack;
	
	Grid(vec3f volMin, vec3f volMax, GLfloat cellSpacing):
	m_volMin(volMin),
	m_volMax(volMax),
	m_cellSpacing(cellSpacing),
	m_gridResolution(ceil((volMax - volMin).x() / cellSpacing),ceil((volMax - volMin).y() / cellSpacing), ceil((volMax - volMin).z() / cellSpacing)),
	m_cellsPerStack(m_gridResolution.x() * m_gridResolution.y()),
	m_cells(m_gridResolution.x() * m_gridResolution.y() * m_gridResolution.z())
	{}
	
	void resize(vec3f volMin, vec3f volMax) {
		m_volMin = volMin;
		m_volMax = volMax;
		m_cells.clear();
		m_gridResolution = vec3f(ceil((volMax - volMin).x() / m_cellSpacing),ceil((volMax - volMin).y() / m_cellSpacing), ceil((volMax - volMin).z() / m_cellSpacing));
		m_cellsPerStack = m_gridResolution.x() * m_gridResolution.y();
		int number_of_cells = m_gridResolution.x() * m_gridResolution.y() * m_gridResolution.z();
		for (unsigned int i = 0; i < number_of_cells; i++ ){
			m_cells.push_back(std::vector<Particle*>());
		}
	}
	
	void update(std::vector<Particle> &particles) {
		
		for (std::vector< std::vector<Particle*> >::iterator cell = m_cells.begin(); cell != m_cells.end(); ++cell) {
			cell->clear();
		}
		
		int cellIndex = 0;
		for (unsigned int i = 0; i < particles.size(); i++)
		{
			Particle* particle = &particles.at(i);
			cellIndex = getCellIndex(particle->pos);
			if(cellIndex > 0 && cellIndex <= static_cast<int>(m_cells.size()))
				m_cells[cellIndex].push_back(particle);
		}
	}
	
	int getCellIndex(vec3f pos) {
		// Need to add volMin to prevent the positions from being negative. This makes the lowest position start at 0,0,0
		int cellX = static_cast<int>(floor((pos.x() + abs(m_volMin.x())) / m_cellSpacing));
		int cellY = static_cast<int>(floor((pos.y() + abs(m_volMin.y())) / m_cellSpacing));
		int cellZ = static_cast<int>(floor((pos.z() + abs(m_volMin.z())) / m_cellSpacing));
		
		return cellZ * static_cast<int>(m_cellsPerStack) + cellY * static_cast<int>(m_gridResolution.x()) + cellX;
	}
	
	std::vector<Particle*> findNeighbours(vec3f pos) {
		int cellIndex = getCellIndex(pos);
		int neighbourIndex = 0;
		std::vector<Particle*> neighbours;
		for (int z = - 1; z <= 1; z++) {
			for (int y = - 1; y <= 1; y++) {
				for (int x = - 1; x <= 1; x++) {
					neighbourIndex = cellIndex + z * static_cast<int>(m_cellsPerStack) + y * static_cast<int>(m_gridResolution.x()) + x;
					if (neighbourIndex >= 0 && neighbourIndex < static_cast<int>(m_cells.size())) {
						neighbours.insert(neighbours.end(), m_cells[neighbourIndex].begin(), m_cells[neighbourIndex].end());
					}
				}
			}
		}
		return neighbours;
	}

	void drawGrid(GridMode gridMode)
	{
		for(int x = 0; x < m_gridResolution.x(); x++)
		{
			for(int y = 0; y < m_gridResolution.y(); y++)
			{
				for(int z = 0; z < m_gridResolution.z(); z++)
				{
					int cellIndex = z * static_cast<int>(m_cellsPerStack) + y * static_cast<int>(m_gridResolution.x()) + x;
						glPushMatrix();
						glTranslatef(m_volMin.x() + x*m_cellSpacing, m_volMin.y() + y*m_cellSpacing, m_volMin.z() + z*m_cellSpacing);
						
						switch(gridMode) {
							case ALL: 
							{
								glColor3f(0.2f,0.2f,0.7f);
								glutWireCube(m_cellSpacing);
								break;
							}
							case OCCUPIED_ONLY:
							{
								if (!m_cells[cellIndex].empty()) {
									float r = (rand()%100)/100.0f;
									float g = (rand()%100)/100.0f;
									float b = (rand()%100)/100.0f;
									glColor3f(r, g, b);
									glutWireCube(m_cellSpacing);
								}
								break;
							}
							case MINECRAFT:
							{
								if (!m_cells[cellIndex].empty()) {
									glColor3f(0.2f,0.2f,0.7f);
									glutSolidCube(m_cellSpacing);
								}
								break;
							}
						}
						glPopMatrix();
				}
			}
		}
	}
};